package org.correct.link;

import java.io.BufferedReader;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.Map;
import java.util.Stack;

import org.correct.config.Config;
import org.correct.model.PageInfoItem;
import org.correct.util.PageInfoAccessor;

class Page {
	int out;
	double pagerank;
	LinkedList<Integer> inList;
	LinkedList<Integer> outList;
	
	public Page() {
		out = 0;
		pagerank = 0;
		inList = new LinkedList<Integer>();
		outList = new LinkedList<Integer>();
	}
}

public class LinkAnalyst {
	private BufferedReader reader = null;
	private Page[] plist = null;
	private int maxin = 0;
	private int maxout = 0;
	private int psize = 0;
	private double alpha = 0.15;
	private int nhunit = 10000;
	private int[] tag = null;
	
	public LinkAnalyst() {}
	
	public void setSize(int size) { psize = size; }
	
	public void openFile(String fname) {
		try{
			reader = new BufferedReader(new FileReader(fname));
		} catch (FileNotFoundException e) {
			System.err.println("Input file not found...");
			e.printStackTrace();
		}
	}
	
	public void closeFile() {
		try {
			reader.close();
		} catch (IOException e) {
			System.err.println("Failed to close file...");
			e.printStackTrace();
		}
	}
	
	public void getInOutRec() {
		plist = new Page[psize];
		String line = null;
		ArrayList<String> tlist = null;
		int count = 0;
		int pfrom = 0;
		int pto = 0; 
		try {
			line = reader.readLine();
			while(line != null) {
				tlist = Utility.split(line, ' ');
				if(tlist != null && tlist.size() > 0) {
					pto = Integer.parseInt(tlist.get(0));
					if(plist[pto] == null) plist[pto] = new Page();
					for(int i = 1; i < tlist.size(); i++) {
						pfrom = Integer.parseInt(tlist.get(i));
						if(plist[pfrom] == null) plist[pfrom] = new Page();
						plist[pto].inList.add(pfrom);
						plist[pfrom].outList.add(pto);
						plist[pfrom].out++;
						if(plist[pfrom].out > maxout) maxout = plist[pfrom].out;
					}
					if(plist[pto].inList.size() > maxin) maxin = plist[pto].inList.size();
				}
				count++;
				if(count % 10000 == 0) {
					System.out.println("Line " + count + " finished...");
				}
				line = reader.readLine();
			}
			
		} catch(IOException e) {
			System.err.println("Error when reading...");
			e.printStackTrace();
		}
		
		System.out.println("Record loading finished...");
		System.out.println();
	}
	
	public void simplePageRank(double err) {
		double[] lastpr = new double[psize];
		double[] currentpr = new double[psize];
		
		long tstart = 0;
		long tend = 0;
		long rstart = 0;
		long rend = 0;
		
		tstart = System.nanoTime();
		
		// initialization
		for(int i = 0; i < psize; i++) {
			lastpr[i] = 1.0 / psize;
		}
		// tn rounds to go
		double tsum = 0;
		int tfrom = 0;
		double error = 1;
		for(int round = 0; error > err; round++) {
			rstart = System.nanoTime();
			for(int i = 0; i < psize; i++) {
				tsum = 0;
				currentpr[i] = alpha / psize;
				Iterator<Integer> it = plist[i].inList.iterator();
				while(it.hasNext()) {
					tfrom = it.next();
					tsum += lastpr[tfrom] / plist[tfrom].out;
				}
				currentpr[i] += (1 - alpha) * tsum;
			}
			error = 0;
			for(int i = 0; i < psize; i++) {
				if(currentpr[i] - lastpr[i] > error) {
					error = currentpr[i] - lastpr[i];
				} else if (lastpr[i] - currentpr[i] > error){
					error = lastpr[i] - currentpr[i];
				}
				lastpr[i] = currentpr[i];
			}
			rend = System.nanoTime();
			System.out.println("Simple pagerank round " + (round + 1) + " finished. "
					+ "Error = " + error + ", Time = " + (rend - rstart) /1000000 + "ms.");
		}
		
		// get final values
		for(int i = 0; i < psize; i++) {
			plist[i].pagerank = currentpr[i];
		}
		tend = System.nanoTime();
		System.out.println("Simple pagerank calculation finished... "
				+ "Time = " + (tend - tstart) / 1000000 + "ms.");
		System.out.println();
	}
	
	public void standardPageRank(double err) {
		double[] init = new double[psize];
		
		long tstart = 0;
		long tend = 0;
		long rstart = 0;
		long rend = 0;
		
		tstart = System.nanoTime();
		
		// initialization
		for(int i = 0; i < psize; i++) {
			init[i] = alpha / psize;
			plist[i].pagerank = 1.0 / psize;
		}
		
		// tn rounds to
		double error = 1;
		for(int round = 0; error > err; round++) {
			rstart = System.nanoTime();
			for(int i = 0; i < psize; i++) {
				Iterator<Integer> it = plist[i].outList.iterator();
				int j = 0;
				while(it.hasNext()) {
					j = it.next();
					init[j] += (1 - alpha) * plist[i].pagerank / plist[i].out;
				}
				if(plist[i].out == 0) {
					for(j = 0; j < psize; j++)
						init[j] += (1 - alpha) * plist[i].pagerank / psize;
				}
				if(i % 10000 == 0) {
					System.out.println("Round " + (round + 1) + ", i = " + i);
				}
			}
			error = 0;
			for(int i = 0; i < psize; i++) {
				if(plist[i].pagerank - init[i] > error) {
					error = plist[i].pagerank - init[i];
				} else if(init[i] - plist[i].pagerank > error) {
					error = init[i] - plist[i].pagerank;
				}
				plist[i].pagerank = init[i];
				init[i] = alpha / psize;
			}
			rend = System.nanoTime();
			System.out.println("Standard pagerank round " + (round + 1) + " finished. "
					+ "Error = " + error + ", Time = " + (rend - rstart) / 1000000 + "ms.");
		}
		tend = System.nanoTime();
		System.out.println("Standard pagerank calculation finished... " 
				+ "Time = " + (tend - tstart) / 1000000 + "ms.");
		System.out.println();
	}
	
	public void fastPageRank(double err) {
		double lastsum = 0;
		double currentsum = 0;
		double[] init = new double[psize];
		
		long tstart = 0;
		long tend = 0;
		long rstart = 0;
		long rend = 0;
		
		tstart = System.nanoTime();
		
		// initialization
		for(int i = 0; i < psize; i++) {
			init[i] = alpha / psize;
			plist[i].pagerank = 1.0 / psize;
			if(plist[i].out == 0)
				lastsum += plist[i].pagerank;
		}
		
		// calculation
		double error = 1;
		for(int round = 0; error > err; round++) {
			rstart = System.nanoTime();
			for(int i = 0; i < psize; i++) {
				Iterator<Integer> it = plist[i].outList.iterator();
				int j = 0;
				while(it.hasNext()) {
					j = it.next();
					init[j] += (1 - alpha) * plist[i].pagerank / plist[i].out;
				}
			}
			currentsum = 0;
			error = 0;
			for(int i = 0; i < psize; i++) {
				if(plist[i].pagerank - (init[i] + lastsum / psize) > error) {
					error = plist[i].pagerank - (init[i] + lastsum / psize);
				} else if(init[i] + lastsum / psize - plist[i].pagerank > error) {
					error = init[i] + lastsum / psize - plist[i].pagerank;
				}
				plist[i].pagerank = init[i] + lastsum / psize;
				init[i] = alpha / psize;
				if(plist[i].out == 0) currentsum += plist[i].pagerank;
			}
			lastsum = currentsum;
			
			rend = System.nanoTime();
			
			System.out.println("Fast pagerank Round " + (round + 1) + " finished. "
					+ "Error = " + error + ", time = " + (rend - rstart) / 1000000 + "ms.");
		}
		
		tend = System.nanoTime();
		System.out.println("Fast pagerank finished... Time = " + (tend - tstart) / 1000000 + "ms.");
		System.out.println();
	}
	
	public void writePRToFile(String fname) {
		double maxpr = 0;
		double minpr = 1;
		try {
			PrintWriter simpleWriter = new PrintWriter(fname);
			for(int i = 0; i < psize; i++) {
				simpleWriter.println(plist[i].pagerank);
				if(plist[i].pagerank > maxpr) maxpr = plist[i].pagerank;
				else if(plist[i].pagerank < minpr) minpr = plist[i].pagerank;
			}
			simpleWriter.flush();
			simpleWriter.close();
		} catch(FileNotFoundException e) {
			System.out.println("Failed to open " + fname + " file...");
			e.printStackTrace();
		}
	}
	
	public void writePageRank(String fileName, HashMap<String, Integer> map) {
		
		String[] id = new String[psize];
		for(Map.Entry<String, Integer> entry : map.entrySet()) {
			id[entry.getValue()] = entry.getKey();
		}
		
		try {
			PrintWriter simpleWriter = new PrintWriter(fileName);
			for(int i = 0; i < psize; i++)
				simpleWriter.println(id[i] + " " + plist[i].pagerank);
			simpleWriter.flush();
			simpleWriter.close();
		} catch(FileNotFoundException e) {
			System.out.println("Failed to open " + fileName + " file...");
			e.printStackTrace();
		}
	}
	
	public void updatePageInfo(HashMap<String, Integer> map) {
		PageInfoAccessor a = new PageInfoAccessor(Config.PAGE_INFO_PATH);
		
		String[] id = new String[psize];
		for(Map.Entry<String, Integer> entry : map.entrySet()) {
			id[entry.getValue()] = entry.getKey();
		}
		
		HashMap<Integer, Object> prmap = new HashMap<Integer, Object>();
		
		for(int i = 0; i < psize; i++)
			prmap.put(Integer.parseInt(id[i]), plist[i].pagerank);
		
		a.load();
		a.updateInfo(PageInfoItem.InfoType.pageRank, prmap);
		a.savePageInfo();
	}
	
	public static void main(String args[]) {
		// Preprocessing: change string filenames to integers
		Preprocessor pre = new Preprocessor();
		pre.openInputFile("inlinks");
		pre.openOutputFile("inlinks_preprocessed");
		int nrec = pre.preprocess();
		pre.closeOutputFile();
		pre.closeInputFile();
		
		// Processing: process the processed file
		LinkAnalyst myAnalyst = new LinkAnalyst();
		myAnalyst.openFile("inlinks_preprocessed");
		System.out.println("File open successfully!");
		
		myAnalyst.setSize(nrec);
		myAnalyst.getInOutRec();
		
		double err = 1E-7;
		// myAnalyst.simplePageRank(err);
		// myAnalyst.standardPageRank(err);
		myAnalyst.fastPageRank(err);
		myAnalyst.writePageRank("pagerank", pre.getMap());
		myAnalyst.closeFile();
	}
}